package com.kcloud.commons.tools.codebuilder.toolkit;

import com.kcloud.commons.tools.codebuilder.exception.TableNotExistedException;

import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * 数据库数据处理工具类
 *
 * @author songhuiqing
 */
public class DBUtils {


    private static Map<Integer, Class<?>> jdbcJavaTypes;

    static {
        jdbcJavaTypes = new TreeMap<>();
        jdbcJavaTypes.put(new Integer(Types.LONGNVARCHAR), String.class);  // -16 字符串
        jdbcJavaTypes.put(new Integer(Types.NCHAR), String.class);    // -15 字符串
        jdbcJavaTypes.put(new Integer(Types.NVARCHAR), String.class);   // -9 字符串
        jdbcJavaTypes.put(new Integer(Types.ROWID), String.class);    // -8 字符串
        jdbcJavaTypes.put(new Integer(Types.BIT), Boolean.class);    // -7 布尔
        jdbcJavaTypes.put(new Integer(Types.TINYINT), Byte.class);    // -6 数字
        jdbcJavaTypes.put(new Integer(Types.BIGINT), Long.class);    // -5 数字
        jdbcJavaTypes.put(new Integer(Types.LONGVARBINARY), Blob.class);  // -4 二进制
        jdbcJavaTypes.put(new Integer(Types.VARBINARY), Blob.class);   // -3 二进制
        jdbcJavaTypes.put(new Integer(Types.BINARY), Blob.class);    // -2 二进制
        jdbcJavaTypes.put(new Integer(Types.LONGVARCHAR), String.class);  // -1 字符串
        jdbcJavaTypes.put(new Integer(Types.CHAR), String.class);    // 1 字符串
        jdbcJavaTypes.put(new Integer(Types.NUMERIC), BigDecimal.class);  // 2 数字
        jdbcJavaTypes.put(new Integer(Types.DECIMAL), BigDecimal.class);  // 3 数字
        jdbcJavaTypes.put(new Integer(Types.INTEGER), Integer.class);   // 4 数字
        jdbcJavaTypes.put(new Integer(Types.SMALLINT), Short.class);   // 5 数字
        jdbcJavaTypes.put(new Integer(Types.FLOAT), BigDecimal.class);   // 6 数字
        jdbcJavaTypes.put(new Integer(Types.REAL), BigDecimal.class);   // 7 数字
        jdbcJavaTypes.put(new Integer(Types.DOUBLE), BigDecimal.class);  // 8 数字
        jdbcJavaTypes.put(new Integer(Types.VARCHAR), String.class);   // 12 字符串
        jdbcJavaTypes.put(new Integer(Types.BOOLEAN), Boolean.class);   // 16 布尔
        jdbcJavaTypes.put(new Integer(Types.DATE), Date.class);    // 91 日期
        jdbcJavaTypes.put(new Integer(Types.TIME), Date.class);    // 92 日期
        jdbcJavaTypes.put(new Integer(Types.TIMESTAMP), Date.class);   // 93 日期
        jdbcJavaTypes.put(new Integer(Types.OTHER), Object.class);    // 1111 其他类型？
        jdbcJavaTypes.put(new Integer(Types.BLOB), Blob.class);    // 2004 二进制
        jdbcJavaTypes.put(new Integer(Types.CLOB), Clob.class);    // 2005 大文本
        jdbcJavaTypes.put(new Integer(Types.NCLOB), Clob.class);    // 2011 大文本
    }

    /**
     * 获取数据库表信息
     *
     * @param jdbcTemplate jdbcTemplate
     * @param tableName    表名
     * @return 数据表结果对象，包含表名、描述及列信息
     */
    public static DBTable getTable(JdbcTemplate jdbcTemplate, String tableName) {
        return getTable(jdbcTemplate.getDataSource(), tableName);
    }

    /**
     * 获取数据库表信息
     *
     * @param ds        数据源对象
     * @param tableName 表名
     * @return 数据表结果对象，包含表名、描述及列信息
     */
    public static DBTable getTable(DataSource ds, String tableName) {
        Connection conn;
        try {
            conn = ds.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException("获取数据源连接异常", e);
        }
        return getTable(conn, tableName);
    }

    /**
     * 获取数据库表信息，要获取表的备注信息，请确保设置了
     * <code>useInformationSchema=true</code>及<code>remarks=true</code>
     * HikariDataSource设置示例如下：<br>
     * HikariConfig config = new HikariConfig(); <br>
     * config.addDataSourceProperty("useInformationSchema", true); <br>
     * config.addDataSourceProperty("remarks", true); <br>
     *
     * @param conn      数据库连接
     * @param tableName 表名
     * @return 数据表结果对象，包含表名、描述及列信息
     */
    public static DBTable getTable(Connection conn, String tableName) {
        DBTable table = new DBTable();
        List<DBTableColumn> columns = new ArrayList<>();
        try {
            DatabaseMetaData metaData = conn.getMetaData();
            String catalog = conn.getCatalog();
            //获取表对象
            ResultSet tables = metaData.getTables(catalog, null, tableName, new String[]{"TABLE"});
            if (tables.next()) {
                table.setName(tables.getString("TABLE_NAME"));
                table.setRemark(tables.getString("REMARKS"));
            } else {
                throw new TableNotExistedException(String.format("你要查询的表%s在当前数据库中不存在!", tableName));
            }
            //获取数据表所有的列
            ResultSet cols = metaData.getColumns(catalog, null, tableName, null);
            while (cols.next()) {
                columns.add(new DBTableColumn(
                        cols.getString("COLUMN_NAME"),
                        jdbcToJavaType(cols.getInt("DATA_TYPE")),
                        cols.getString("REMARKS"),
                        "YES".equalsIgnoreCase(cols.getString("IS_NULLABLE")),
                        false, false));
            }
            //获取主键
            ResultSet primaryKeys = metaData.getPrimaryKeys(catalog, null, tableName);
            if (primaryKeys.next()) {
                String pk = primaryKeys.getString("COLUMN_NAME");
                columns.stream().filter(col -> col.getName().equalsIgnoreCase(pk)).forEach(col -> col.setPrimaryKey(true));
            }
            //获取外键
            ResultSet importedKeys = metaData.getImportedKeys(catalog, null, tableName);
            if (importedKeys.next()) {
                String fk = importedKeys.getString("FKCOLUMN_NAME");
                columns.stream().filter(col -> col.getName().equalsIgnoreCase(fk)).forEach(col -> col.setForeignKey(true));
            }
            table.setColumns(columns);
        } catch (SQLException e) {
            throw new RuntimeException("获取数据库表异常", e);
        }
        return table;
    }

    public static Class<?> jdbcToJavaType (int jdbcType) {
        return jdbcJavaTypes.get(jdbcType);
    }

    public static void main(String[] args) throws Exception{
//        HikariConfig config = new HikariConfig();
//        config.setDriverClassName("com.mysql.cj.jdbc.Driver");
//        config.setJdbcUrl("jdbc:mysql://118.190.159.137:3306/demo?useSSL=false&serverTimezone=UTC");
//        config.setUsername("root");
//        config.setPassword("gang317");
//        config.addDataSourceProperty("useInformationSchema", true);
//        config.addDataSourceProperty("remarks", true);
//        DataSource ds = new HikariDataSource(config);
//        DBTable table = getTable(ds, "AUTHORIZED_OBJECT");
//        System.out.println(table.toString());
    }
}
